Release 10.1A: OpenEdge Development:
ProDataSets


Building the server side change handler

On the server side, there can also be a generic procedure that applies all changes to a ProDataSet. As we explained in Chapter 6, "Updating Data with ProDataSets,", there is a SAVE-ROW-CHANGES method to apply one row of changes to the database, but not a SAVE-CHANGES method for a temp-table or for the entire ProDataSet. The reason for this is there might be many ways in which you need to control the update process, including:

This section shows you a sample for a general purpose procedure that makes default decisions about these variants. Each row is a single transaction. Tables are processed from the top of the hierarchy down (and multiple top-level tables are processed in the order in which they appear in the ProDataSet definition). And changes are processed in ROW-STATE Order. You could, of course, extend or modify this procedure for other defaults and to accept parameters to change the behavior.

The example procedure is commitChanges.p. It handles changes to any ProDataSet with any number of tables in its hierarchy. It takes the ProDataSet handle as its INPUT-OUTPUT parameter. The variables represent the handle to the current top-level buffer and a buffer counter, as shown:

/* commitChanges.p */ 
DEFINE INPUT-OUTPUT PARAMETER DATASET-HANDLE hDataSet. 
DEFINE VARIABLE hTopBuff    AS HANDLE     NO-UNDO. 
DEFINE VARIABLE iBuff       AS INTEGER    NO-UNDO. 

The procedure starts its main loop through all top-level buffers, using the NUM-TOP-BUFFERS counter and the GET-TOP-BUFFER method to return each one in turn. These are the temp-table buffers with no parent. They are returned in the order they appear in the ProDataSet definition. There is one special case for which the loop must check. Because of the FILL behavior, children of a REPOSITION Data-Relation show up in the list of top-level buffers, even though they aren’t really top-level for the purpose of walking through the hierarchy of the ProDataSet as this procedure does. For this reason, the loop contains a check to see if there is a PARENT-RELATION for the buffer. If there is, then it’s really a child of a reposition relation, not a true top-level buffer, and it is skipped.

For each top-level buffer, the recursive procedure traverseBuffers is run to walk down that branch of the ProDataSet definition, as shown:

DO  iBuff = 1 TO hDataSet:NUM-TOP-BUFFERS: 
        hTopBuff = hDataSet:GET-TOP-BUFFER(iBuff). 
         
        IF hTopBuff:PARENT-RELATION NE ? THEN 
            NEXT.   /* Skip the reposition children. */ 
         
        RUN traverseBuffers (hTopBuff). 
END.  /* END DO iBuff */ 

Procedure traverseBuffers serves only to recurse down through any number of levels of parent-child tables. It runs another internal procedure saveBuffer on itself and then loops through each child of the current buffer. NUM-CHILD-RELATIONS returns the number of relations for which this buffer is the parent, and GET-CHILD-RELATION returns each one in turn. Since GET-CHILD-RELATION returns the handle of the Data-Relation object, you need to follow that to its CHILD-BUFFER to get the buffer handle of each of the current’s buffer’s children, as shown:

PROCEDURE traverseBuffers: 
   DEFINE INPUT  PARAMETER phBuffer AS HANDLE     NO-UNDO. 
   DEFINE VARIABLE iChildRel AS INTEGER    NO-UNDO. 
         
   RUN saveBuffer(phBuffer). 
   DO iChildRel = 1 TO phBuffer:NUM-CHILD-RELATIONS: 
      RUN traverseBuffers 
          (phBuffer:GET-CHILD-RELATION(iChildRel):CHILD-BUFFER). 
      END.         /* END DO iChildRel */ 
END PROCEDURE.   /* traverseBuffers */ 

Procedure saveBuffer does the actual work of running SAVE-ROW-CHANGES, as shown:

PROCEDURE saveBuffer: 
DEFINE INPUT  PARAMETER phBuffer AS HANDLE     NO-UNDO. 
DEFINE VARIABLE hBeforeBuff AS HANDLE     NO-UNDO. 
DEFINE VARIABLE hBeforeQry  AS HANDLE     NO-UNDO. 
         
hBeforeBuff = phBuffer:BEFORE-BUFFER. 

The buffer handle passed in is actually the after-table buffer for the current table. You want to run SAVE-ROW-CHANGES on the before-table buffer, especially since in the case of a Delete there is no after-table row to process. Variable hBeforeBuff points to that buffer.

The VALID-HANDLE test checks whether there is a before-table for this temp-table at all. If the table does not have a BEFORE-TABLE in its definition and has not been enabled for update by setting TRACKING-CHANGES to true, then there will not be a before-table. Otherwise, the before-table could contain zero, one, or many rows, depending on how many rows in that table have been changed. The query simply walks through all those rows and runs SAVE-ROW-CHANGES on each one. SAVE-ROW-CHANGES starts its own transaction if there is none active, so each change is saved independently. If the save fails because of invalid data or because it had been changed by another user, Progress sets the ERROR attribute on the row. The procedure checks this and also sets the REJECTED attribute, so that the caller knows row by row which rows were successfully updated into the database and which ones were not, as shown:

 IF VALID-HANDLE(hBeforeBuff) THEN 
        DO: 
            CREATE QUERY hBeforeQry. 
            hBeforeQry:ADD-BUFFER(hBeforeBuff). 
            hBeforeQry:QUERY-PREPARE("FOR EACH " + hBeforeBuff:NAME). 
            hBeforeQry:QUERY-OPEN(). 
            hBeforeQry:GET-FIRST(). 
            DO WHILE NOT hBeforeQry:QUERY-OFF-END: 
                hBeforeBuff:SAVE-ROW-CHANGES(). 
                /* If there was an error signal that this row  
                   did not make it into the database. */ 
                IF hBeforeBuff:ERROR THEN 
                   hBeforeBuff:REJECTED = YES. 
                hBeforeQry:GET-NEXT(). 
            END.  /* END DO WHILE NOT QUERY-OFF-END */ 
            DELETE OBJECT hBeforeQry. 
        END.      /* END DO IF VALID-HANDLE */ 
END PROCEDURE. /* saveChanges */ 

Remember that SAVE-ROW-CHANGES takes two optional arguments:

Because this is a generic save procedure, there is no straightforward way to specify these parameters if they’re needed. This example uses the default.

That’s the end of the code for commitChanges.p.


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095